﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Net;
using System.Net.Mail;
using System.Text;

namespace DotNet.DevOps.Tasks.Cmdlets
{
    /// <summary>
    /// Can send an email, using strings supplied and/or template for body, with the option to replace fields in the template.
    /// Sends via SMTP supporting credentials, ssl,
    /// 
    /// </summary>
    /// <example>
    /// import-module .\DotNet.DevOps.Tasks.dll
    /// 
    /// Send-Email -Property @{
    ///                            From="noreply@gmail.com" 
    ///                            To="Helpdesk@gmail.com"
    ///                            Subject="Email test"
    ///                            Body="This is an test email."
    ///                            SmtpServerUrl="smtp.gmail.com"
    ///                            SmtpServerPort=587
    ///                            TemplateFields @{
    ///                                   "key1"="Value1"
    ///                                   "key2"="Value2"
    ///                                   "key3"="Value3"
    ///                            }
    ///                       }
    /// </example>
    [Cmdlet(VerbsCommunications.Send, "Email", ConfirmImpact = ConfirmImpact.None, SupportsShouldProcess = false, SupportsTransactions = false)]
    public class Send_Email : Cmdlet
    {
        /// <summary>
        /// Sender email address
        /// </summary>
        [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public string From { get; set; }

        /// <summary>
        /// Receiver(s) of the email. Separate by comma ',' for multiple receivers.
        /// </summary>
        [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public string To { get; set; }

        /// <summary>
        /// Subject
        /// </summary>
        [Parameter(Mandatory = true, Position = 2, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public string Subject { get; set; }

        /// <summary>
        /// Email body. 
        /// </summary>
        [Parameter(Mandatory = true, Position = 3, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public string Body { get; set; }

        /// <summary>
        /// Smtp server url for ex: smtp.gmail.com
        /// </summary>
        [Parameter(Mandatory = true, Position = 4, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public string SmtpServerUrl { get; set; }

        /// <summary>
        /// Smtp server port number. Default port 25. Port 587 should be specified if using submission.
        /// </summary>
        [Parameter(Mandatory = false, Position = 5, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public int SmtpServerPort { get; set; }

        /// <summary>
        /// Username. Network credentials. Ex. johndoe@gmail.com    Will default enable SSL if SSL is not deliberatly deactivated
        /// </summary>
        [Parameter(Mandatory = false, Position = 6, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public string Username { get; set; }

        /// <summary>
        /// Password. Network credentials. Ex. johndoesecretpassword    Will default enable SSL if SSL is not deliberatly deactivated
        /// </summary>
        [Parameter(Mandatory = false, Position = 7, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public string Password { get; set; }

        /// <summary>
        /// Use SSL. Default true if supplying credentials. Else default false.
        /// </summary>
        [Parameter(Mandatory = false, Position = 8, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public bool UseSSL { get; set; }

        /// <summary>
        /// File to use as template. If the template is in html format, the mail will automaticallly be in html format. If a template is specified it WILL overwrite any body content defined in the Body property.
        /// </summary>
        [Parameter(Mandatory = false, Position = 9, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public string TemplateFile { get; set; }

        /// <summary>
        /// Send emails as html
        /// </summary>
        [Parameter(Mandatory = false, Position = 10, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public bool AsHtml { get; set; }

        /// <summary>
        /// Display name instead of the From email address.
        /// </summary>
        [Parameter(Mandatory = false, Position = 11, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public string FromDisplayName { get; set; }

        /// <summary>
        /// Fields to replace in template. All fields in the template must use the format [fieldName].
        /// </summary>
        [Parameter(Mandatory = false, Position = 12, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
        public Dictionary<string,string> TemplateFields { get; set; }

        private SmtpClient _client;
        private PowershellCmdletLogger _log;
        public Send_Email()
        {
            _log = new PowershellCmdletLogger(this);
            TemplateFields = new Dictionary<string, string>();
            SmtpServerPort = (SmtpServerPort == 0 ? 21 : SmtpServerPort);
            AsHtml = false;
            UseSSL = true;
            Username = Password = String.Empty;
        }

        protected override void BeginProcessing()
        {
            _client = new SmtpClient(SmtpServerUrl, SmtpServerPort) 
            { 
                EnableSsl = UseSSL
            };

            if (string.IsNullOrEmpty(Username) == false)
            {
                _client.Credentials = new NetworkCredential(Username, Password);
            }
        }

        protected override void ProcessRecord()
        {
            var email = GenerateEmail();

            _log.LogInfo(string.Format("Sending email to: {0} from: {1}", email.To, email.From));

            _client.Send(email);

            _log.LogInfo("Email sent.");
        }

        private MailMessage GenerateEmail()
        {
            var email = new MailMessage();
            email.To.Add(To);
            email.From = new MailAddress(From, FromDisplayName);
            email.Subject = Subject;
            email.Body = GenerateBody();
            email.IsBodyHtml = AsHtml;
            return email;
        }

        private string GenerateBody()
        {
            var body = GetBodyFromSource();

            body = ReplaceCustomFieldsInBody(body);

            EvaluateBody(body);

            return body;
        }

        private string GetBodyFromSource()
        {
            return string.IsNullOrEmpty(TemplateFile) == false ? GetTemplateBody() : Body;
        }

        private string GetTemplateBody()
        {
            var body = ReadTemplate();

            DecideEmailFormatBasedOnTemplateFormat(body);

            return body;
        }

        private string ReadTemplate()
        {
            if(File.Exists(TemplateFile))
            {
                return File.ReadAllText(TemplateFile);
            }

            _log.LogError(new Exception(string.Format("TemplateFile '{0}' not found. No email has been sent.", TemplateFile)));

            return null;
        }

        private void EvaluateBody(string body)
        {
            if(string.IsNullOrEmpty(body))
               _log.LogError(new Exception("No email sent as the body is empty. Please check your parameters and/or template file."));
        }

        private void DecideEmailFormatBasedOnTemplateFormat(string templateContent)
        {
            AsHtml = false;

            if (templateContent.ToLower().Contains("<html>"))
                AsHtml = true;

            _log.LogInfo(string.Format("Using template '{0}', in {1} format.", TemplateFile, (AsHtml ? "'html'" : "'plain text'")));
        }

        private string ReplaceCustomFieldsInBody(string body)
        {
            TemplateFields.Keys.ToList()
                .ForEach(key =>
                             {
                                 var rawKey = GetRawKey(key);
                                 var newValue = GetNewValue(key);
                                 body = body.Replace(rawKey, newValue);
                                 _log.LogInfo(string.Format("Updated field(s): {0}={1}",rawKey,newValue));
                             });
            return body;
        }

        private string GetRawKey(string key)
        {
            return string.Format("[{0}]", key.Replace("[", "").Replace("]", ""));
        }

        private string GetNewValue(string key)
        {
            return TemplateFields[key];
        }
    }
}
